package com.pig4cloud.pig.gateway.filter;

import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.Mode;
import cn.hutool.crypto.Padding;
import cn.hutool.crypto.symmetric.AES;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.pig4cloud.pig.common.core.constant.CacheConstants;
import com.pig4cloud.pig.common.core.exception.ValidateCodeException;
import com.pig4cloud.pig.common.swagger.config.FilterIgnorePropertiesConfig;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.rewrite.CachedBodyOutputMessage;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.reactive.function.server.HandlerStrategies;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.util.List;
import java.util.function.Function;

import static cn.hutool.core.codec.Base64.decode;
import static java.nio.charset.StandardCharsets.UTF_8;

@Slf4j
public abstract class BaseAbstractGatewayFilter extends AbstractGatewayFilterFactory {

    protected final List<HttpMessageReader<?>> messageReaders = HandlerStrategies.withDefaults().messageReaders();

    protected static final String PASSWORD = "password";
    protected static final String TOKEN = "token";
    protected static final String KEY_ALGORITHM = "AES";

    @Value("${security.encode.key:1234567812345678}")
    protected String encodeKey;
    @Autowired
    protected ObjectMapper objectMapper;
    @Autowired
    protected RedisTemplate redisTemplate;
    @Autowired
    protected FilterIgnorePropertiesConfig filterIgnorePropertiesConfig;

    @Override
    public GatewayFilter apply(Object config) {
        return (exchange, chain) -> applys(exchange,chain,exchange.getResponse(),exchange.getRequest());
    }

    /**
     * 共用过滤
     * @param exchange
     * @param chain
     * @param response
     * @return
     */
    abstract Mono<Void> applys(ServerWebExchange exchange, GatewayFilterChain chain, ServerHttpResponse response, ServerHttpRequest request);

    /**
     * 简单的basic认证
     *
     * @param exchange 上下文
     * @return 是否有权限
     */
    protected boolean hasAuth(ServerWebExchange exchange) {
        String auth = exchange.getRequest().getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
        log.info("Basic认证信息为：{}", auth);
        return true;
    }

    /**
     * 原文解密
     *
     * @return
     */
    protected Function decryptAES() {
        return s -> {
            // 构建前端对应解密AES 因子
            AES aes = new AES(Mode.CBC, Padding.NoPadding, new SecretKeySpec(encodeKey.getBytes(), KEY_ALGORITHM),
                    new IvParameterSpec(encodeKey.getBytes()));

            // 获取请求密码并解密
            var inParamsMap = JSONUtil.toBean(JSONUtil.toJsonPrettyStr(HttpUtil.decodeParamMap((String) s, CharsetUtil.CHARSET_UTF_8)), Dict.class);

            // 返回修改后报文字符
            return Mono.just(HttpUtil.toParams(inParamsMap.set(PASSWORD, new String(aes.decrypt(decode(inParamsMap.getStr(PASSWORD).getBytes(UTF_8))),
                    UTF_8).trim())));
        };
    }

    /**
     * 报文转换
     *
     * @return
     */
    protected ServerHttpRequestDecorator decorate(ServerWebExchange exchange, HttpHeaders headers,
                                                CachedBodyOutputMessage outputMessage) {
        return new ServerHttpRequestDecorator(exchange.getRequest()) {
            @Override
            public HttpHeaders getHeaders() {
                long contentLength = headers.getContentLength();
                HttpHeaders httpHeaders = new HttpHeaders();
                httpHeaders.putAll(super.getHeaders());
                if (contentLength > 0) {
                    httpHeaders.setContentLength(contentLength);
                } else {
                    httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
                }
                return httpHeaders;
            }

            @Override
            public Flux<DataBuffer> getBody() {
                return outputMessage.getBody();
            }
        };
    }

    /**
     * 检查code
     *
     * @param request
     */
    @SneakyThrows
    protected void checkCode(ServerHttpRequest request) {
        String code = request.getQueryParams().getFirst("code");

        if (StrUtil.isBlank(code)) {
            throw new ValidateCodeException("验证码不能为空");
        }

        String randomStr = request.getQueryParams().getFirst("randomStr");

        //https://gitee.com/log4j/pig/issues/IWA0D
        String mobile = request.getQueryParams().getFirst("mobile");
        randomStr = StrUtil.isNotBlank(mobile) ? mobile : randomStr;

        String key = CacheConstants.DEFAULT_CODE_KEY + randomStr;
        redisTemplate.setKeySerializer(new StringRedisSerializer());

        if (!redisTemplate.hasKey(key)) {
            throw new ValidateCodeException("验证码不合法");
        }

        Object codeObj = redisTemplate.opsForValue().get(key);

        if (codeObj == null) {
            throw new ValidateCodeException("验证码不合法");
        }

        String saveCode = codeObj.toString();
        if (StrUtil.isBlank(saveCode) | !StrUtil.equals(saveCode, code)) {
            redisTemplate.delete(key);
            throw new ValidateCodeException("验证码不合法");
        }

        redisTemplate.delete(key);
    }

}
